package base.thread_example.example01;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 多生产者多消费者生产消费问题: 做到生产/消费轮流交替,不要求生产者1/生产者2轮流交替生产,同理消费者也不要求轮流消费.
 */
public class MultipleProducerAndConsumer {
    public static void main(String[] args) {
        Resource r = new Resource("烤鸭");

        Producer p = new Producer(r);
        Consumer c = new Consumer(r);

        // Runnable用来表示协议接口的，故线程执行同一类任务用同一个协议接口即可
        new Thread(p,"生产者-1").start();
        new Thread(c,"消费者-1").start();
        new Thread(p,"生产者-2").start();
        new Thread(c,"消费者-2").start();
    }
}


// 生产者
class Producer implements Runnable{
    private Resource r;
    public Producer(Resource r){
        this.r = r;
    }
    @Override
    public void run() {
        while (true) {
            try {
                r.set();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 消费者
class Consumer implements Runnable {
    private Resource r;
    public Consumer(Resource r){
        this.r = r;
    }
    @Override
    public void run() {
        while (true){
            try {
                r.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Resource {
    private static final Logger log = LoggerFactory.getLogger(Resource.class);

    private String name;
    private boolean flag=false;//资源标识：一开始表示没有资源
    public Resource(String name){
        this.name = name;
    }

    // 生产方法
    public synchronized void set() throws InterruptedException {
        // 多生产者被唤醒后需要再次判断是否生产资源已经存在.因此用的是while循环
        while (this.flag){//有资源,进入wait,等待资源被消费.while解决虚假唤醒同类线程,比如虚假唤醒了生产者线程
            this.wait();
        }
        //Thread.sleep(30);//模拟生产耗时
        this.flag = !this.flag;//告知消费者线程资源生产完毕
        log.info("----------------->{} 生产了 {}", Thread.currentThread().getName(), this.name);
        this.notifyAll();//唤醒所有wait线程.注意："所有"是指因同一把同步互斥锁导致进入休眠的线程,此处指Resource对象上绑定的操作系统中的Monitor
    }

    // 消费方法
    public synchronized void get() throws InterruptedException {
        // 多消费者被唤醒后需要再次判断是否生产资源已被消费,因此用while循环。
        while (!this.flag){//没有资源,进入wait,等待资源生产
            this.wait();
        }
        //Thread.sleep(20);//模拟消费耗时
        log.info("{} 消费了 {}", Thread.currentThread().getName(), this.name);
        ////this.name = "";//消费掉资源,不能这样会出问题
        this.flag = !this.flag;//告知生产者线程资源已被消费
        this.notifyAll();
    }

}
